{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### InceptionNet\n",
    "    InceptionNet诞生于2014年，当年ImageNet竞赛冠军，Top5错误率为6.67%\n",
    "    Szegedy C, Liu W, Jia Y, et al. Going Deeper with Convolutions. In CVPR, 2015.\n",
    "    \n",
    "    InceptionNet引入Inception结构块\n",
    "    在同一层网络内使用不同尺寸的卷积核，提升了模型感知力\n",
    "    使用了批标准化，缓解了梯度消失\n",
    "    \n",
    "    使用1*1卷积核，作用到输入特征图的每一个像素点，\n",
    "    通过设定少于输入特征图深度的1*1卷积核个数，\n",
    "    减少了输出特征图深度，起到了降维的作用。\n",
    "    减少了参数量核计算量。\n",
    "    \n",
    "###### Inception结构块\n",
    "    一个Inception结构块包含四个分支：\n",
    "    ①1*1卷积核→卷积连接器\n",
    "    ②1*1卷积核→3*3卷积核→卷积连接器\n",
    "    ③1*1卷积核→5*5卷积核→卷积连接器\n",
    "    ④3*3maxpooling→1*1卷积核→卷积连接器\n",
    "    \n",
    "    送到卷积连接器的四个特征数据尺寸相同，卷积连接器会把收到的四路特征数据按深度方向拼接，形成Inception结构块的输出\n",
    "    \n",
    "    结构：\n",
    "    ①\n",
    "    C（核：16*1*1，步长：1，填充：same ）\n",
    "    B（Yes）\n",
    "    A（relu）\n",
    "    P（None）\n",
    "    D（None）\n",
    "    \n",
    "    ②\n",
    "    C（核：16*1*1，步长：1，填充：same）         C（核：16*3*3，步长：1，填充：same ）\n",
    "    B（Yes）                            B（Yes）\n",
    "    A（relu）                           A（relu）\n",
    "    P（None）                           P（None）\n",
    "    D（None）                           D（None）\n",
    "    \n",
    "    ③\n",
    "    C（核：16*1*1，步长：1，填充：same）         C（核：16*5*5，步长：1，填充：same ）\n",
    "    B（Yes）                            B（Yes）\n",
    "    A（relu）                           A（relu）\n",
    "    P（None）                           P（None）\n",
    "    D（None）                           D（None）\n",
    "    \n",
    "    ④\n",
    "    C（None）                           C（核：16*1*1，步长：1，填充：same ）\n",
    "    B（None）                           B（Yes）\n",
    "    A（None）                           A（relu）\n",
    "    P（Max，核：3*3，步长：1，填充：same）        P（None）\n",
    "    D（None）                           D（None）\n",
    "    \n",
    "###### Inception结构\n",
    "    in：32*32\n",
    "    \n",
    "    C（核：16*3*3，步长：1，填充：same ）\n",
    "    B（Yes）\n",
    "    A（relu）\n",
    "    P（None）\n",
    "    D（None）\n",
    "    \n",
    "    block_0：\n",
    "        ①②③④\n",
    "        ①②③④\n",
    "    \n",
    "    block_1：\n",
    "        ①②③④\n",
    "        ①②③④\n",
    "        \n",
    "    global avgpooling\n",
    "    \n",
    "    Dense 10"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import tensorflow as tf\n",
    "import os\n",
    "import numpy as np\n",
    "from matplotlib import pyplot as plt\n",
    "from tensorflow.keras.layers import Conv2D, BatchNormalization, Activation, MaxPool2D, Dropout, Flatten, Dense, \\\n",
    "    GlobalAveragePooling2D\n",
    "from tensorflow.keras import Model\n",
    "\n",
    "np.set_printoptions(threshold=np.inf)\n",
    "\n",
    "cifar10 = tf.keras.datasets.cifar10\n",
    "(x_train, y_train), (x_test, y_test) = cifar10.load_data()\n",
    "x_train, x_test = x_train / 255.0, x_test / 255.0\n",
    "\n",
    "####定义一个卷积、批标准化、Relu激活函数操作的类，减少代码数量，增加代码的可读性####\n",
    "class ConvBNRelu(Model):\n",
    "    def __init__(self, ch, kernelsz=3, strides=1, padding='same'):#默认卷积核3*3，步长1，全零填充\n",
    "        super(ConvBNRelu, self).__init__()\n",
    "        self.model = tf.keras.models.Sequential([\n",
    "            Conv2D(ch, kernelsz, strides=strides, padding=padding),\n",
    "            BatchNormalization(),\n",
    "            Activation('relu')\n",
    "        ])\n",
    "\n",
    "    def call(self, x):\n",
    "        x = self.model(x, training=False) #在training=False时，BN通过整个训练集计算均值、方差去做批归一化，training=True时，通过当前batch的均值、方差去做批归一化。推理时 training=False效果好\n",
    "        return x\n",
    "\n",
    "\n",
    "class InceptionBlk(Model):\n",
    "    def __init__(self, ch, strides=1):\n",
    "        super(InceptionBlk, self).__init__()\n",
    "        self.ch = ch\n",
    "        self.strides = strides\n",
    "        \n",
    "        self.c1 = ConvBNRelu(ch, kernelsz=1, strides=strides)\n",
    "        \n",
    "        self.c2_1 = ConvBNRelu(ch, kernelsz=1, strides=strides)\n",
    "        self.c2_2 = ConvBNRelu(ch, kernelsz=3, strides=1)\n",
    "        \n",
    "        self.c3_1 = ConvBNRelu(ch, kernelsz=1, strides=strides)\n",
    "        self.c3_2 = ConvBNRelu(ch, kernelsz=5, strides=1)\n",
    "        \n",
    "        self.p4_1 = MaxPool2D(3, strides=1, padding='same')#最大池化\n",
    "        self.c4_2 = ConvBNRelu(ch, kernelsz=1, strides=strides)\n",
    "\n",
    "    def call(self, x):\n",
    "        x1 = self.c1(x)\n",
    "        x2_1 = self.c2_1(x)\n",
    "        x2_2 = self.c2_2(x2_1)\n",
    "        x3_1 = self.c3_1(x)\n",
    "        x3_2 = self.c3_2(x3_1)\n",
    "        x4_1 = self.p4_1(x)\n",
    "        x4_2 = self.c4_2(x4_1)\n",
    "        # concat along axis=channel\n",
    "        x = tf.concat([x1, x2_2, x3_2, x4_2], axis=3)#将Inception结构块中4个分支的特征数据按照深度的方向拼接\n",
    "        return x\n",
    "\n",
    "####搭建InceptionNet网络结构####\n",
    "class Inception10(Model):\n",
    "    def __init__(self, num_blocks, num_classes, init_ch=16, **kwargs):#默认输出深度为16\n",
    "        super(Inception10, self).__init__(**kwargs)\n",
    "        self.in_channels = init_ch\n",
    "        self.out_channels = init_ch\n",
    "        self.num_blocks = num_blocks\n",
    "        self.init_ch = init_ch\n",
    "        \n",
    "        self.c1 = ConvBNRelu(init_ch)\n",
    "        \n",
    "        self.blocks = tf.keras.models.Sequential()\n",
    "        for block_id in range(num_blocks):\n",
    "            for layer_id in range(2):\n",
    "                if layer_id == 0:\n",
    "                    block = InceptionBlk(self.out_channels, strides=2)#步长为2，输出特征图尺寸减半\n",
    "                else:\n",
    "                    block = InceptionBlk(self.out_channels, strides=1)\n",
    "                self.blocks.add(block)\n",
    "            # enlarger out_channels per block\n",
    "            #加深输出特征图深度，尽可能保持特征抽取中信息的承载量一致\n",
    "            # block_0的通道数16，\n",
    "            # block_1通道数32，block1经过四个分支后输出深度为4*32=128\n",
    "            self.out_channels *= 2\n",
    "        #将128个通道的数据平均池化    \n",
    "        self.p1 = GlobalAveragePooling2D()\n",
    "        #送入10个分类的全连接\n",
    "        self.f1 = Dense(num_classes, activation='softmax')\n",
    "\n",
    "    def call(self, x):\n",
    "        x = self.c1(x)\n",
    "        x = self.blocks(x)\n",
    "        x = self.p1(x)\n",
    "        y = self.f1(x)\n",
    "        return y\n",
    "\n",
    "\n",
    "model = Inception10(num_blocks=2, num_classes=10)#实例化Inception10的类，指定InceptionNet的block数为2，指定网络为十分类\n",
    "\n",
    "model.compile(optimizer='adam',\n",
    "              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),\n",
    "              metrics=['sparse_categorical_accuracy'])\n",
    "\n",
    "checkpoint_save_path = \"./checkpoint/Inception10.ckpt\"\n",
    "if os.path.exists(checkpoint_save_path + '.index'):\n",
    "    print('-------------load the model-----------------')\n",
    "    model.load_weights(checkpoint_save_path)\n",
    "\n",
    "cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_save_path,\n",
    "                                                 save_weights_only=True,\n",
    "                                                 save_best_only=True)\n",
    "\n",
    "history = model.fit(x_train, y_train, batch_size=32, epochs=5, validation_data=(x_test, y_test), validation_freq=1,\n",
    "                    callbacks=[cp_callback])\n",
    "model.summary()\n",
    "\n",
    "# print(model.trainable_variables)\n",
    "file = open('./weights.txt', 'w')\n",
    "for v in model.trainable_variables:\n",
    "    file.write(str(v.name) + '\\n')\n",
    "    file.write(str(v.shape) + '\\n')\n",
    "    file.write(str(v.numpy()) + '\\n')\n",
    "file.close()\n",
    "\n",
    "###############################################    show   ###############################################\n",
    "\n",
    "# 显示训练集和验证集的acc和loss曲线\n",
    "acc = history.history['sparse_categorical_accuracy']\n",
    "val_acc = history.history['val_sparse_categorical_accuracy']\n",
    "loss = history.history['loss']\n",
    "val_loss = history.history['val_loss']\n",
    "\n",
    "plt.subplot(1, 2, 1)\n",
    "plt.plot(acc, label='Training Accuracy')\n",
    "plt.plot(val_acc, label='Validation Accuracy')\n",
    "plt.title('Training and Validation Accuracy')\n",
    "plt.legend()\n",
    "\n",
    "plt.subplot(1, 2, 2)\n",
    "plt.plot(loss, label='Training Loss')\n",
    "plt.plot(val_loss, label='Validation Loss')\n",
    "plt.title('Training and Validation Loss')\n",
    "plt.legend()\n",
    "plt.show()\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
